라우팅과 Bootstrap Pagination Component를 사용한 페이지 처리

✒️ 2025-05-28 10:29 내용 수정



작성 흐름

  1. 먼저 페이지 처리를 위해 전체 데이터 수, 버튼에 표시할 페이지 수, 그리고 한 페이지에 표시할 데이터 수를 정한다.
전체 데이터 100개
버튼에 표시할 페이지 3개
한 페이지에 표시할 데이터 10개
  1. 모든 데이터를 표시하기 위해 필요한 전체 페이지 수를 계산한다.
    • totalData/limit으로 계산하고, 나누고 남는 데이터도 모두 담아야 하므로 Math.ceil()을 사용해 계산 결과와 가장 가깝고 그 중에서 최소값인 정수를 얻는다.
    • totalData%limit != 0일 때 totalData/limit + 1 한 것과 동일한 결과다.
const totalPage = Math.ceil(totalData/limit); // 전체 페이지 수
  1. Pagination의 시작점을 offset으로 설정하고, offset은 query string에 따라 바뀔 예정이므로 useState()를 사용하여 State로 관리했다.
    • useState()Hook과 State를, useEffect()useEffect 참고.
    • 초기값은 1로 설정하고, 초기 렌더링 때 query string을 확인하여 새로 설정해준다.
    • query string의 값이 없거나 1 이하라면 초기값을 1로 고정하고, 그보다 크면 (값-1)로 설정하여 Pagination 버튼의 중앙에 활성화된 숫자가 오도록 offset을 설정했다.
    • useSearchParams()useHref()React-Router Hooks 참고.
const [searchParams, setSearchParams] = useSearchParams(); // 현재 page

const [offset, setOffset] = useState(1); // pagination 시작점
const navigate = useNavigate(); // 이동 처리(Link 대신 useNavigate() 사용)

useEffect(()=>{ // 최초 렌더링 때 offset을 query string으로 전달된 page 값을 사용해서 결정
	let newOffset = !searchParams.get("page") || parseInt(searchParams.get("page") - 1) < 1  ? 
	1 : parseInt(searchParams.get("page") - 1);
	setOffset(newOffset);
}, []);
  1. query string이 바뀔 때마다 렌더링을 다시 하도록 useEffect()를 설정한다.
useEffect(()=>{ // query string이 바뀌면 활성화된 버튼을 변경
	setActivePage(!searchParams.get("page") ? 1 : parseInt(searchParams.get("page")));
}, [searchParams]);
  1. 출력할 Component를 만들 함수를 설정하고, 이 함수에서 offset과 버튼에 표시할 페이지 개수를 사용하여 출력할 Component의 수를 결정한다.
// pagination 숫자 설정
function pageBlock() { 
	let blockComponent = []; // 렌더링 할 Component를 담을 배열
	for(let i = 0; i < blockPerPage; i++) { // blockPerPage 수 만큼만 표시
		const pageNumber = offset + i; // 페이지 숫자는 시작점으로부터 blockPerPage 개수만큼
		if (pageNumber <= totalPage) { // 총 페이지 수를 넘어가지 않도록 설정
			blockComponent.push(
				<Pagination.Item key={pageNumber}
				active={pageNumber == activePage} // 페이지가 현재 활성화된 페이지랑 같다면 "active" property true
				onClick={()=>{handleClickNumber(pageNumber)}}>
				{pageNumber}
				</Pagination.Item>
			);
		}
	}
	return blockComponent;
}
  1. Pagination에서 처음 번호 이동, 이전 번호로 이동, 다음 번호로 이동, 마지막 번호로 이동하는 버튼들을 눌렀을 때 번호 변경을 설정한다.
// 이전 버튼 눌렀을 때 번호 처리
function handlePrev() { 
	if (offset > blockPerPage) { // 시작점이 blockPerPage보다 클 때 1씩 감소
		setOffset(offset - 1);
	} else {
		setOffset(1); // 시작점이 blockPerPage보다 작으면 1로 고정
	}
}

// 다음 버튼 눌렀을 때 번호 처리
function handleNext() { 
	if (offset + blockPerPage < totalPage) { // (시작점 + blockPerPage)이 총 페이지보다 작으면 1씩 증가
		setOffset(offset + 1);
	} else {
		handleLast(); // 마지막 가기 버튼과 동일하므로 아래 함수로 처리
	}
}

// 마지막으로 가기 버튼 눌렀을 때 번호 처리
function handleLast() { 
	if (totalPage > blockPerPage) { // 총 페이지 수가 blockPerPage보다 클 때 마지막 페이지를 포함해서 3개 출력
		setOffset(totalPage - blockPerPage + 1);
	} else { // 작다면 1로 고정
		setOffset(1);
	}
}

//.. 생략

// 처음 번호로 이동 버튼은 Pagination 시작점인 offset을 1로 설정하면 끝
return(
	<Pagination.First onClick={()=>{setOffset(1)}}/>
)
  1. 부가적인 설정까지 끝내면 url을 사용한 Pagination을 설정할 수 있다.
import 'bootstrap/dist/css/bootstrap.min.css'; // bootstrap 사용
import { useEffect, useState } from "react";
import { Pagination } from "react-bootstrap"; // react-bootstrap의 Pagination Component
import { useHref, useNavigate, useSearchParams } from "react-router-dom";

function PageTest() {
    const blockPerPage = 3; // 버튼에 표시할 페이지 수
    const totalData = 100; // 전체 데이터 수
    const limit = 10; // 한 페이지에 표시할 데이터 수

    const [searchParams, setSearchParams] = useSearchParams(); // 현재 page
    
    const totalPage = Math.max(Math.ceil(totalData/limit), 1); // 전체 페이지 수

    const [offset, setOffset] = useState(1); // pagination 시작점
    const [activePage, setActivePage] = useState( // 활성화된 페이지
        !searchParams.get("page") ? 1 : searchParams.get("page")
    ); 
    const url = useHref(); // 경로
    const navigate = useNavigate(); // 이동 처리(Link 대신 useNavigate() 사용)

    useEffect(()=>{ // 최초 렌더링 때 offset을 query string으로 전달된 page 값을 사용해서 결정
        let newOffset = !searchParams.get("page") || parseInt(searchParams.get("page") - 1) < 1  ? 
        1 : parseInt(searchParams.get("page") - 1);
        setOffset(newOffset);
    }, []);

    useEffect(()=>{ // query string이 바뀌면 활성화된 버튼을 변경
        setActivePage(!searchParams.get("page") ? 1 : parseInt(searchParams.get("page")));
    }, [searchParams]);

    // 버튼 누를 때 처리
    function handleClickNumber(pageNumber) { 
            setActivePage(pageNumber); // pagination 숫자 활성화 설정
            navigate(`${url}?page=${pageNumber}`); // url을 사용한 페이지 이동 처리
    }

    // pagination 숫자 설정
    function pageBlock() { 
        let blockComponent = []; // 렌더링 할 Component를 담을 배열
        for(let i = 0; i < blockPerPage; i++) { // blockPerPage 수 만큼만 표시
            const pageNumber = offset + i; // 페이지 숫자는 시작점으로부터 blockPerPage 개수만큼
            if (pageNumber <= totalPage) { // 총 페이지 수를 넘어가지 않도록 설정
                blockComponent.push(
                    <Pagination.Item key={pageNumber}
                    active={pageNumber == activePage} // 페이지가 현재 활성화된 페이지랑 같다면 
									                //"active" property true
                    onClick={()=>{handleClickNumber(pageNumber)}}>
                    {pageNumber}
                    </Pagination.Item>
                );
            }
        }
        return blockComponent;
    }

    // 이전 버튼 눌렀을 때 번호 처리
    function handlePrev() { 
        if (offset > blockPerPage) { // 시작점이 blockPerPage보다 클 때 1씩 감소
            setOffset(offset - 1);
        } else {
            setOffset(1); // 시작점이 blockPerPage보다 작으면 1로 고정
        }
    }

    // 다음 버튼 눌렀을 때 번호 처리
    function handleNext() { 
        if (offset + blockPerPage < totalPage) { 
            setOffset(offset + 1); // (시작점 + blockPerPage)이 총 페이지보다 작으면 1씩 증가
        } else {
            handleLast(); // 마지막 가기 버튼과 동일하므로 아래 함수로 처리
        }
    }

    // 마지막으로 가기 버튼 눌렀을 때 번호 처리
    function handleLast() { 
        if (totalPage > blockPerPage) { // 총 페이지 수가 blockPerPage보다 클 때 
            setOffset(totalPage - blockPerPage + 1); // 마지막 페이지를 포함해서 3개 출력
        } else { // 작다면 1로 고정
            setOffset(1);
        }
    }

    return(
        <div className='d-flex justify-content-center'>
	        <Pagination>
		        {/* 첫 페이지로 돌아가기 버튼은 offset을 1로 설정한다. */}
	            <Pagination.First onClick={()=>{setOffset(1)}}/>
	            <Pagination.Prev onClick={handlePrev}/>
	
	            {
		            // <Pagination.Item> Component들이 반환된다.
	                pageBlock()
	            }
	
	            <Pagination.Next onClick={handleNext}/>
	            <Pagination.Last onClick={handleLast}/>
	        </Pagination>
        </div>
    )
}

export default PageTest;

pagination 1.png
pagination 2.png

pagination 3.png

pagination 4.png

pagination 5.png

pagination 6.png
pagination 7.png